/**
 * \file  antiviral_imp.cpp
 * \brief Definition of Antiviral implementation class.
 *
 * LAV: Lib Anti Virals
 * <http://lav.googlecode.com/>
 * Copyright (C) 2010 Velez Ezequiel, Peralta Andres, FuDePAN
 *
 * This file is part of the LAV project.
 *
 * Contents:       Cpp file for LAV providing class Antiviral implementation.
 *
 * System:         LAV
 * Language:       C++
 *
 * Author:         Velez Ezequiel
 * E-Mail:         e.s.velez@gmail.com
 *
 * Author:         Peralta Andres
 * E-Mail:         andres.vhd@gmail.com
 *
 * Author:         Boretto Martin
 * E-Mail:         martinboretto@gmail.com
 *
 * Author:         Lenarduzzi Federico
 * E-Mail:         federicolenarduzzi@gmail.com
 *
 * Author:         Alberione Fernando
 * E-Mail:         falberione037@gmail.com
 *
 * Author:         Ocampo Emanuel
 * E-Mail:         eocampo86@gmail.com
 *
 * LAV is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LAV is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LAV.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "lav/antiviral_imp.h"

using namespace std;

namespace lav
{

static const int PRINT_PRESICION = 2;

std::ostream& operator<< (std::ostream& out, const AvType& atype)
{
    switch (atype)
    {
        case tRT:
            out << "tRT";
            break;
        case tPI:
            out << "tPI";
            break;
        case tComb:
            out << "tComb";
            break;
        default:
            out.setstate(std::ios::failbit); //set error bit in is
    }
    return out;
}


std::ostream& operator<< (std::ostream& out, const AvClass& aclass)
{
    switch (aclass)
    {
        case cPI:
            out << "cPI";
            break;
        case cNRTI:
            out << "cNRTI";
            break;
        case cNNRTI:
            out << "cNNRTI";
            break;
        case cComb:
            out << "cComb";
            break;
        default:
            out.setstate(std::ios::failbit); //set error bit in is
    }
    return out;
}


std::istream& operator>> (std::istream& is, AvType& atype)
{
    string s;

    if (is >> s)
    {
        if (s == "tRT")
            atype = tRT;
        else if (s == "tPI")
            atype = tPI;
        else if (s == "tComb")
            atype = tComb;
        else
            is.setstate(std::ios::failbit); //set error bit in is
    }
    return is;
}


std::istream& operator>> (std::istream& is, AvClass& aclass)
{
    string s;

    if (is >> s)
    {
        if (s == "cPI")
            aclass = cPI;
        else if (s == "cNRTI")
            aclass = cNRTI;
        else if (s == "cNNRTI")
            aclass = cNNRTI;
        else if (s == "cComb")
            aclass = cComb;
        else
            is.setstate(std::ios::failbit); //set error bit in is
    }
    return is;
}

biopp::NucDistance ResisBasedAntiviral::ResistancePosition::min_dist_to_position(const biopp::Triplet& triplet) const
{
    biopp::NucDistance min_dist = MAX_VAL;
    biopp::NucDistance current_dist;
    for (size_t i = 0u; i < aminoacids.size(); ++i)
    {
        current_dist = triplet.min_distance(aminoacids[i]);
        mili::minimize(min_dist, current_dist);
    }
    return min_dist;
}

biopp::NucDistance ResisBasedAntiviral::min_dist_to_antiviral(const PossibleMutants::TripletPositionContainer& sequence_mutant) const
{
    biopp::NucDistance min_dist = MAX_VAL;
    PossibleMutants::TripletPositionContainer::const_iterator it_sequence;
    ResistancesGroupContainer::const_iterator it_group;
    for (it_sequence = sequence_mutant.begin(); it_sequence != sequence_mutant.end(); ++it_sequence)
    {
        it_group = resGroups.begin();
        while (it_group != resGroups.end() && min_dist > ZERO_DISTANCE)
        {
            min_dist = min_dist_to_group(it_group, it_sequence);
            ++it_group;
        }
    }
    return min_dist;
}


void ResisBasedAntiviral::ResistancePosition::min_dist_to_resistance(const biopp::PseudoNucSequence& sequence, TripletVal& min_list) const
{
    OptimizedContainer<TripletSet, biopp::NucDistance, MinimumValues, IncludingZero> keep_min_value(min_list.first, MAX_VAL);
    const biopp::PseudoTriplet pos_triplet = sequence.get_triplet(pos);
    for (size_t i = 0u; i < aminoacids.size(); ++i)
    {
        // Processing aminoacids
        biopp::TripletsIterator it_triplets;
        for (it_triplets = biopp::GeneticCode::triplets(aminoacids[i]); !it_triplets.end(); ++it_triplets)
        {
            min_dist_between_triplets(it_triplets, pos_triplet, keep_min_value);
        }
    }
    min_list.second = keep_min_value.getBestValue();
}


void ResisBasedAntiviral::ResistancePosition::expand_aminoacid(const biopp::PseudoNucSequence& sequence, TripletList& triplets) const
{
    const biopp::PseudoTriplet pos_triplet = sequence.get_triplet(pos);
    for (size_t i = 0u; i < aminoacids.size(); ++i)
    {
        // Processing aminoacids
        biopp::TripletsIterator it_triplets;
        for (it_triplets = biopp::GeneticCode::triplets(aminoacids[i]); !it_triplets.end(); ++it_triplets)
        {
            get_pure_triplets(pos_triplet, it_triplets, triplets);
        }
    }
}


void ResisBasedAntiviral::combine_with(ResisBasedAntiviral& other) const
{
    // Obtains the resistances of the first antiviral group (this)
    mili::CAutonomousIterator<ResisBasedAntiviral::ResistancePositionContainer> it(resGroups.front().resistances);

    while (!it.end())
    {
        combine_currently(it, other);
        ++it;
    }
}

void ResisBasedAntiviral::mutate_sequence(const biopp::PseudoNucSequence& sequence, MutantDist& mutations) const
{
    biopp::NucDistance min_dist = applies(sequence);
    if (min_dist > biopp::NucDistance(0))
    {
        mili::CAutonomousIterator<ResistancePositionContainer> it(resGroups.front().resistances);

        while (!it.end())
        {
            TripletVal tv;
            it->min_dist_to_resistance(sequence, tv);
            biopp::NucDistance min_val = tv.second;
            TripletSet triplets = tv.first;
            mili::CAutonomousIterator<TripletSet> trip_iter(triplets);

            while (!trip_iter.end())
            {
                biopp::PseudoNucSequence mutation = sequence;
                if (min_dist == min_val)
                {
                    mutation.set_triplet(it->pos, *trip_iter);
                    mutations.add_mutant(mutation);
                }
                ++trip_iter;
            }
            ++it;
        }
        mutations.set_distance(min_dist);
    }
}


biopp::NucDistance ResisBasedAntiviral::applies(const biopp::PseudoNucSequence& sequence) const
{
    mili::FirstTimeFlag firstAssignment;
    biopp::NucDistance min_dist = 1.0f; // arbitrary value, since first time is governed by the firstAssignment flag
    ResistancesGroupContainer::const_iterator groups_it = resGroups.begin();
    while (groups_it != resGroups.end() && min_dist > 0)
    {
        const biopp::NucDistance groupDist = groups_it->get_group_distance(sequence);

        if (firstAssignment)
            min_dist = groupDist;
        else
            mili::minimize(min_dist, groupDist);

        ++groups_it;
    }

    return min_dist;
}


biopp::NucDistance ResisBasedAntiviral::ResistancesGroup::get_group_distance(const biopp::PseudoNucSequence& sequence) const
{
    mili::FirstTimeFlag firstAssignment;
    float min_dist = -1.0f; // arbitrary value, since first time is governed by the firstAssignment flag

    if (isValid())
    {
        Combination result;
        // This should be improved using a new combination policy which uses a predicate to generate combinations
        expand_newtonian <ResisBasedAntiviral::ResistancePosition> (resistances, 1, resistances.size(), result) ;

        // For each combination generated, the weight and distance are analized.
        Combination::const_iterator it = result.begin();
        while (it != result.end())
        {
            double currentWeight = get_acumulated_weight(*it);
            if (currentWeight >= minResistanceWeight)
            {
                if (firstAssignment)
                    min_dist = get_combination_distance(*it, sequence);
                else
                    mili::minimize(min_dist, get_combination_distance(*it, sequence));
            }

            if (min_dist == 0)
                it = result.end();
            else
                ++it;
        }
    }
    else
        throw SystemError("Resistance positions weight are not enough to achieve a resistance (ResisBasedAntiviral::get_group_distance");

    return min_dist;
}


void ResisBasedAntiviral::ResistancesGroup::generate_power_set(Combination& power_set) const
{
    /**
     * Inner class which determinates what will do with each combination generated
     */
    class IsolatedObserver: public comb_eng::CombinationObserver<ResistancePosition>
    {
    public:

        IsolatedObserver(double g_min) : group_min(g_min)
        {}

        /**
         * This method determinates what will it do with the current combination
         * @param combination Current combination to will be processed
         */
        void update(const ResistancePositionContainer& combination)
        {
            mili::insert_into(combinations, combination);
        }


        /**
         * This method returns the result of the processing
         * @param out All combinations processed will be here
         */
        void get_result(Combination& out)
        {
            out = combinations;
        }

    private:

        Combination combinations;
        double group_min;
    };

    // Observer creation
    IsolatedObserver* observer = new IsolatedObserver(minResistanceWeight);
    WeightPredicate predicate(minResistanceWeight);
    CombinationPolicyPredicate<ResistancePosition, WeightPredicate>* predicate_comb_policy;
    comb_eng::Status status;
    Combination new_combinations;

    // The combination policy is created. Represents the condition to generate a combination or not
    predicate_comb_policy = new CombinationPolicyPredicate<ResistancePosition, WeightPredicate> (observer, "ExpNewtonian", predicate);

    // The combine method is applied to generate the weight combination
    predicate_comb_policy->combine(resistances, status);

    // The resut is saved in new_combinations
    observer->get_result(new_combinations);

    // The combination is ready to be inserted
    power_set.insert(power_set.begin(), new_combinations.begin(), new_combinations.end());

    delete(observer);
    delete(predicate_comb_policy);
}


void ResisBasedAntiviral::print_resistances() const
{

    mili::CAutonomousIterator<ResistancesGroupContainer> it_group(resGroups);
    int i(1);
    while (!it_group.end())
    {
        // Get and print the value of minResistanceWeight
        clog << "- Group " << i << std::endl;
        clog << "MinResistanceWeight: " << it_group->minResistanceWeight << std::endl;

        // Print resistances
        mili::CAutonomousIterator<ResistancePositionContainer> it_resistances(it_group->resistances);
        clog.precision(PRINT_PRESICION);
        while (!it_resistances.end())
        {
            clog << "(" << it_resistances->pos << ", " << it_resistances->aminoacids << ", " << it_resistances->weight << ")" << endl;
            ++it_resistances;
        }
        ++it_group;
        ++i;
    }
}

// -----------------------
// Private methods section
// -----------------------


void ResisBasedAntiviral::combine_currently(const mili::CAutonomousIterator<ResisBasedAntiviral::ResistancePositionContainer>& it,
        ResisBasedAntiviral& other) const
{
    size_t pos_temp = it->pos;
    std::string amin_temp = it->aminoacids;

    // Obtains the resistances of the first antiviral group (other)
    mili::AutonomousIterator<ResisBasedAntiviral::ResistancePositionContainer> it2(other.resGroups.front().resistances);
    bool found(false);

    while (!it2.end())
    {
        if (pos_temp == it2->pos)
        {
            // Checks same positions
            add_aminoacids(amin_temp, it2->aminoacids);
            found = true;
        }
        ++it2;
    }
    if (!found)
        // Other is not worked on the 'pos_temp', so a new position is added
        other._old_add_resistance(pos_temp + 1, amin_temp);

}

biopp::NucDistance ResisBasedAntiviral::min_dist_to_group(const ResistancesGroupContainer::const_iterator& it_group,
        const PossibleMutants::TripletPositionContainer::const_iterator& it_sequence) const
{
    biopp::NucDistance min_dist = MAX_VAL;
    biopp::NucDistance current_min_dist;
    ResisBasedAntiviral::ResistancePositionContainer::const_iterator it_resistances = it_group->resistances.begin();
    while (it_resistances != it_group->resistances.end() && min_dist > ZERO_DISTANCE)
    {
        if (it_sequence->pos == it_resistances->pos)
        {
            current_min_dist = it_resistances->min_dist_to_position(it_sequence->triplet);
            mili::minimize(min_dist, current_min_dist);
        }
        ++it_resistances;
    }
    return min_dist;
}


void ResisBasedAntiviral::ResistancePosition::min_dist_between_triplets(const biopp::TripletsIterator& it_triplets,
        const biopp::PseudoTriplet& pos_triplet,
        OptimizedContainer<TripletSet, biopp::NucDistance, MinimumValues, IncludingZero>& keep_min_value) const
{
    biopp::TripletsIterator it_pure_triplets;
    for (it_pure_triplets = pos_triplet.getPureTriplets(); !it_pure_triplets.end(); ++it_pure_triplets)
    {
        // Processing pure triplets
        const biopp::NucDistance distance = (*it_triplets).distance(*it_pure_triplets, _matrix);
        keep_min_value.add(*it_triplets, distance);
    }
}


void ResisBasedAntiviral::ResistancePosition::get_pure_triplets(const biopp::PseudoTriplet& pos_triplet,
        const biopp::TripletsIterator& it_triplets, TripletList& triplets) const
{
    // Processing 'pseudo' triplets
    for (biopp::TripletsIterator itPureT = pos_triplet.getPureTriplets(); !itPureT.end(); ++itPureT)
    {
        // The current triplet will be inserted in the result 'triplets'
        mili::insert_into(triplets, (*it_triplets));
    }
}


void ResisBasedAntiviral::add_aminoacids(const std::string& amin_temp, std::string& aminoacids) const
{
    for (size_t i = 0u; i < amin_temp.size(); ++i)
    {
        if (aminoacids.find(amin_temp[i]) == string::npos)
        {
            // Combine aminoacids from both antivirals
            aminoacids += amin_temp[i];
        }
    }
}

}